/*
 * lwm, a window manager for X11
 * Copyright (C) 1997-2003 Elliott Hughes, James Carter
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <errno.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <signal.h>

#include <X11/X.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>

#include "lwm.h"

Mode mode;			/* The window manager's mode. (See "lwm.h".) */
int start_x;			/* The X position where the mode changed. */
int start_y;			/* The Y position where the mode changed. */

Display * dpy;			/* The connection to the X server. */
int screen_count;		/* The number of screens. */
ScreenInfo * screens;		/* Information about these screens. */
ScreenInfo * current_screen;

XFontSet font_set = NULL;	/* Font set for title var */
XFontSetExtents *font_set_ext = NULL;
XFontSet popup_font_set = NULL;	/* Font set for popups */
XFontSetExtents *popup_font_set_ext = NULL;

Bool shape;			/* Does server have Shape Window extension? */
int shape_event;		/* ShapeEvent event type. */

/* Atoms we're interested in. See the ICCCM for more information. */
Atom wm_state;
Atom wm_change_state;
Atom wm_protocols;
Atom wm_delete;
Atom wm_take_focus;
Atom wm_colormaps;
Atom compound_text;

/** Netscape uses this to give information about the URL it's displaying. */
Atom _mozilla_url;

/*
 * if we're really short of a clue we might look at motif hints, and
 * we're not going to link with motif, so we'll have to do it by hand
 */
Atom motif_wm_hints;

char *argv0;

static void initScreens(void);
static void initScreen(int);

/*ARGSUSED*/
extern int
main(int argc, char *argv[]) {
	XEvent ev;
	struct sigaction sa;
	int dpy_fd, max_fd;

	argv0 = argv[0];

	mode = wm_initialising;

	setlocale(LC_ALL,"");

	/* Open a connection to the X server. */
	dpy = XOpenDisplay(NULL);
	if (dpy == 0)
		panic("can't open display.");

	parseResources();

	/* Set up an error handler. */
	XSetErrorHandler(errorHandler);

	/* Set up signal handlers. */
	signal(SIGTERM, Terminate);
	signal(SIGINT, Terminate);
	signal(SIGHUP, Terminate);

	/* Ignore SIGCHLD. */
	sa.sa_handler = SIG_IGN;
#ifdef SA_NOCLDWAIT
	sa.sa_flags = SA_NOCLDWAIT;
#else
	sa.sa_flags = 0;
#endif
	sigemptyset(&sa.sa_mask);
	sigaction(SIGCHLD, &sa, 0);

	/* Internalize useful atoms. */
	wm_state = XInternAtom(dpy, "WM_STATE", False);
	wm_change_state = XInternAtom(dpy, "WM_CHANGE_STATE", False);
	wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False);
	wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
	wm_take_focus = XInternAtom(dpy, "WM_TAKE_FOCUS", False);
	wm_colormaps = XInternAtom(dpy, "WM_COLORMAP_WINDOWS", False);
	compound_text = XInternAtom(dpy, "COMPOUND_TEXT", False);
	
	_mozilla_url = XInternAtom(dpy, "_MOZILLA_URL", False);

	motif_wm_hints = XInternAtom(dpy, "_MOTIF_WM_HINTS", False);

	ewmh_init();
	
	/*
	 * Get fonts for our titlebar and our popup window. We try to
	 * get Lucida, but if we can't we make do with fixed because everyone
	 * has that.
	 */
	{
		/* FIXME: do these need to be freed? */
		char **missing;
		char *def;
		int missing_count;

		font_set = XCreateFontSet(dpy, font_name,
			&missing, &missing_count, &def);
		if (font_set == NULL)
			font_set = XCreateFontSet(dpy, "fixed",
				&missing, &missing_count, &def);
		if (font_set == NULL)
			panic("unable to create font set for title font");
		if (missing_count > 0)
			fprintf(stderr,"%s: warning: missing %d charset"
				"%s for title font\n", argv0, missing_count,
				(missing_count == 1)?"":"s");
		font_set_ext = XExtentsOfFontSet(font_set);

		popup_font_set = XCreateFontSet(dpy, popup_font_name,
			&missing, &missing_count, &def);
		if (popup_font_set == NULL)
			popup_font_set = XCreateFontSet(dpy, "fixed",
				&missing, &missing_count, &def);
		if (popup_font_set == NULL)
			panic("unable to create font set for popup font");
		if (missing_count > 0)
			fprintf(stderr,"%s: warning: missing %d charset"
				"%s for popup font\n", argv0, missing_count,
				(missing_count == 1)?"":"s");
		popup_font_set_ext = XExtentsOfFontSet(popup_font_set);
	}
	
	initScreens();
	ewmh_init_screens();
	session_init(argc, argv);
	
	/* See if the server has the Shape Window extension. */
	shape = serverSupportsShapes();
	
	/*
	 * Initialisation is finished, but we start off not interacting with the
	 * user.
	 */
	mode = wm_idle;
	
	/*
	 * The main event loop.
	 */
	dpy_fd = ConnectionNumber(dpy);
	max_fd = dpy_fd + 1;
	if (ice_fd > dpy_fd) max_fd = ice_fd + 1;
	for (;;) {
		fd_set readfds;

		FD_ZERO(&readfds);
		FD_SET(dpy_fd, &readfds);
		if (ice_fd > 0) FD_SET(ice_fd, &readfds);
		if (select(max_fd, &readfds, NULL, NULL, NULL) > -1) {
		    if (FD_ISSET(dpy_fd, &readfds)) {
			    while (XPending(dpy)) {
				XNextEvent(dpy, &ev);
				dispatch(&ev);
			    }
		    }
		    if (ice_fd > 0 && FD_ISSET(ice_fd, &readfds)) {
			    session_process();
		    }
		}
	}
}

void
sendConfigureNotify(Client *c) {
	XConfigureEvent ce;

	ce.type = ConfigureNotify;
	ce.event = c->window;
	ce.window = c->window;
	if (c->framed == True) {
		ce.x = c->size.x + border;
		ce.y = c->size.y + border;
		ce.width = c->size.width - 2 * border;
		ce.height = c->size.height - 2 * border;
		ce.border_width = c->border;
	} else {
		ce.x = c->size.x;
		ce.y = c->size.y;
		ce.width = c->size.width;
		ce.height = c->size.height;
		ce.border_width = c->border;
	}
	ce.above = None;
	ce.override_redirect = 0;
	XSendEvent(dpy, c->window, False, StructureNotifyMask, (XEvent *) &ce);
}

extern void
scanWindowTree(int screen) {
	unsigned int i;
	unsigned int nwins;
	Client * c;
	Window dw1;
	Window dw2;
	Window * wins;
	XWindowAttributes attr;
	
	XQueryTree(dpy, screens[screen].root, &dw1, &dw2, &wins, &nwins);
	for (i = 0; i < nwins; i++) {
		XGetWindowAttributes(dpy, wins[i], &attr);
		if (attr.override_redirect /*|| isShaped(wins[i])*/ || wins[i] == screens[screen].popup)
			continue;
		c = Client_Add(wins[i], screens[screen].root);
		if (c != 0 && c->window == wins[i]) {
			c->screen = &screens[screen];
			c->size.x = attr.x;
			c->size.y = attr.y;
/* we'll leave it until it's managed
			if (c->framed == True) {
				c->size.x -= border;
				c->size.y -= border;
			}
*/
			c->size.width  = attr.width;
			c->size.height = attr.height;
/* we'll leave it until it's managed
			if (c->framed == True) {
				c->size.width  += 2 * border;
				c->size.height += 2 * border;
			}
*/
			c->border = attr.border_width;
			if (attr.map_state == IsViewable) {
				c->internal_state = IPendingReparenting;
				manage(c, 1);
			}
		}
	}
	XFree(wins);
}

/*ARGSUSED*/
extern void
shell(ScreenInfo * screen, int button, int x, int y) {
	char * command = NULL;
	char * sh;
	
	/* Get the command we're to execute. Give up if there isn't one. */
	if (button == Button1)
		command = btn1_command;
	if (button == Button2)
		command = btn2_command;
	if (command == NULL)
		return;
	
	sh = getenv("SHELL");
	if (sh == 0)
		sh = "/bin/sh";
	
	switch (fork()) {
	case 0:		/* Child. */
		close(ConnectionNumber(dpy));
		if (screen && screen->display_spec != 0)
			putenv(screen->display_spec);
		execl(sh, sh, "-c", command, NULL);
		fprintf(stderr, "%s: can't exec \"%s -c %s\"\n", argv0, sh,
			command);
		execlp("xterm", "xterm", NULL);
		exit(EXIT_FAILURE);
	case -1:	/* Error. */
		fprintf(stderr, "%s: couldn't fork\n", argv0);
		break;
	}
}

extern int
titleHeight(void) {
	return font_set_ext->max_logical_extent.height;
}

extern int
ascent(XFontSetExtents *font_set_ext) {
	return abs(font_set_ext->max_logical_extent.y);
}

extern int
popupHeight(void) {
	return popup_font_set_ext->max_logical_extent.height;
}

extern int
titleWidth(XFontSet font_set, Client *c) {
	XRectangle ink;
	XRectangle logical;
	char *name;
	int namelen;

	if (c == NULL) return 0;
	if (c->menu_name == NULL) {
		name = c->name;
		namelen = c->namelen;
	} else {
		name = c->menu_name;
		namelen = c->menu_namelen;
	}
	if (name == NULL) return 0;
#ifdef X_HAVE_UTF8_STRING
	if (c->name_utf8 == True) 
		Xutf8TextExtents(font_set, name, namelen,
			&ink, &logical);
	else
#endif
		XmbTextExtents(font_set, name, namelen,
			&ink, &logical);

	return logical.width;
}

extern int
popupWidth(char *string, int string_length) {
	XRectangle ink;
	XRectangle logical;

	XmbTextExtents(popup_font_set, string, string_length,
		&ink, &logical);

	return logical.width;
}

static void
initScreens(void) {
	int screen;
	
	/* Find out how many screens we've got, and allocate space for their info. */
	screen_count = ScreenCount(dpy);
	screens = (ScreenInfo *) malloc(screen_count * sizeof(ScreenInfo));
	
	/* Go through the screens one-by-one, initialising them. */
	for (screen = 0; screen < screen_count; screen++) {
		initialiseCursors(screen);
		initScreen(screen);
		scanWindowTree(screen);
	}
}

static void
initScreen(int screen) {
	XGCValues gv;
	XSetWindowAttributes attr;
	XColor colour, exact;
	int len;
	char * display_string = DisplayString(dpy);
	char * colon = strrchr(display_string, ':');
	char * dot = strrchr(display_string, '.');
	
	/* Set the DISPLAY specification. */
	if (colon) {
		len = 9 + strlen(display_string) + ((dot == 0) ? 2 : 0) + 10;
		screens[screen].display_spec = (char *) malloc(len);
		sprintf(screens[screen].display_spec, "DISPLAY=%s", display_string);
		if (dot == 0) dot = screens[screen].display_spec + len - 3;
		else dot = strrchr(screens[screen].display_spec, '.');
		sprintf(dot, ".%i", screen);
	} else {
		screens[screen].display_spec = 0;
	}

	/* Find the root window. */
	screens[screen].root = RootWindow(dpy, screen);
	screens[screen].display_width = DisplayWidth(dpy, screen);
	screens[screen].display_height = DisplayHeight(dpy, screen);
	screens[screen].strut.left = 0;
	screens[screen].strut.right = 0;
	screens[screen].strut.top = 0;
	screens[screen].strut.bottom = 0;
	
	/* Get the pixel values of the only two colours we use. */
	screens[screen].black = BlackPixel(dpy, screen);
	screens[screen].white = WhitePixel(dpy, screen);
	XAllocNamedColor(dpy, DefaultColormap(dpy, screen), "DimGray", &colour, &exact);
	screens[screen].gray = colour.pixel;
	
	/* Set up root (frame) GC's. */
	gv.foreground = screens[screen].black ^ screens[screen].white;
	gv.background = screens[screen].white;
	gv.function = GXxor;
	gv.line_width = 1;
	gv.subwindow_mode = IncludeInferiors;
	screens[screen].gc_thin = XCreateGC(dpy, screens[screen].root,
		GCForeground | GCBackground | GCFunction |
		GCLineWidth | GCSubwindowMode, &gv);
	
	gv.line_width = 2;
	screens[screen].gc = XCreateGC(dpy, screens[screen].root,
		GCForeground | GCBackground | GCFunction |
		GCLineWidth | GCSubwindowMode, &gv);
	
	/* Create a window for our popup. */
	screens[screen].popup = XCreateSimpleWindow(dpy, screens[screen].root,
		0, 0, 1, 1, 1, screens[screen].black, screens[screen].white);
	attr.event_mask = ButtonMask | ButtonMotionMask | ExposureMask;
	XChangeWindowAttributes(dpy, screens[screen].popup, CWEventMask, &attr);
	
	/* Create menu GC. */
	gv.line_width = 1;
	screens[screen].menu_gc = XCreateGC(dpy, screens[screen].popup,
		GCForeground | GCBackground | GCFunction |
		GCLineWidth | GCSubwindowMode, &gv);
	
	/* Create size indicator GC. */
	gv.foreground = screens[screen].black;
	gv.function = GXcopy;
	screens[screen].size_gc = XCreateGC(dpy, screens[screen].popup,
		GCForeground | GCBackground | GCFunction |
		GCLineWidth | GCSubwindowMode, &gv);
	
	/* Announce our interest in the root window. */
	attr.cursor = screens[screen].root_cursor;
	attr.event_mask = SubstructureRedirectMask | SubstructureNotifyMask |
		ColormapChangeMask | ButtonPressMask | PropertyChangeMask |
		EnterWindowMask;
	XChangeWindowAttributes(dpy, screens[screen].root, CWCursor |
		CWEventMask, &attr);
	
	/* Make sure all our communication to the server got through. */
	XSync(dpy, False);
}

/**
Find the screen for which root is the root window.
*/
ScreenInfo *
getScreenFromRoot(Window root) {
	int screen;
	
	for (screen = 0; screen < screen_count; screen++)
		if (screens[screen].root == root)
			return &screens[screen];
	
	return 0;
}
